﻿//#define _CRT_SECURE_NO_WARNINGS
//#include <stdio.h>
#include "study03.h"
// C/C++ 学习网站：https://legacy.cplusplus.com 注意旧版本（搜索）和新版本

/*
一、构造函数：
    构造函数是类的成员函数，它在对象被创建时被调用；构造函数的名字与类名相同，没有返回类型，不需要 void
        默认的构造函数一般没有参数或只有一个参数（默认有一个参数，即类名 *this）。
    构造函数的作用：
        1. 为对象初始化成员变量
        2. 为对象分配内存
        3. 调用其他成员函数
    构造函数的特点：
        1. 构造函数不能被声明为 const
        2. 构造函数不能被重载
        3. 构造函数不能被虚函数
        4. 构造函数不能被模板化
        5. 构造函数不能被友元函数
    注意：当构造函数放在 private 区域时，该类不能被实例化，相当于告知编译器不需要构造函数;
        所以当类只有一个构造函数时，建议将其放在 public 区域。

二、析构函数：
    析构函数是类的成员函数，它在对象被销毁时被调用；析构函数的名字与类名相同（以 ~ 开头），没有返回类型，
        不需要 void，析构函数一般没有参数或只有一个参数（默认有一个参数，即类名 *this）且不需要重载。
        一般不会主动调用析构函数，因为当对象被销毁时（比如栈区函数调用结束后），由编译器自动调用。
    析构函数的作用和特点与构造函数基本相同。
    注意：在写析构函数时，可以不需要手动写什么栈区释放内存的代码，编译器会自动完成。
        所以一般只是写点提示、打印信息、日志之类的代码，不需要复杂的操作。

三、虚函数（主要用于实现多态）：
    虚函数是类的成员函数，它在基类中定义，在派生类中可以重新定义，可以实现多态。
        虚函数通常通过虚函数表来实现编译。虚函数表包含基类中所有虚函数的入口地址，
        使得运行时能够根据对象的实际类型调用虚函数以正确的覆写函数。
        派生类中可以重新定义虚函数，
    虚函数的声明格式：
        virtual 返回类型 函数名(参数表) const;
    虚函数的特点：
        1. 虚函数可以被重载
        2. 虚函数可以被覆盖
        3. 虚函数可以被覆盖为纯虚函数（没有函数体）
        4. 虚函数可以被覆盖为常函数（常函数不能被虚函数重载）
        5. 虚函数可以被覆盖为虚函数（虚函数不能被常函数重载）
        6. 虚函数可以被覆盖为私有函数（私有函数不能被虚函数重载）
        7. 虚函数可以被覆盖为保护函数（保护函数不能被虚函数重载）
        8. 虚函数可以被覆盖为虚继承（虚继承可以实现多态）
        9. 虚函数可以被覆盖为非虚继承（非虚继承不能实现多态）
    纯虚函数：
        纯虚函数是虚函数的一种特殊形式，它没有函数体，只有纯虚函数声明，没有定义，
        纯虚函数的作用是提供接口，让派生类决定如何实现虚函数；强制派生类实现虚函数；否则也不能实例化对象。
        纯虚函数的声明格式：
            virtual 返回类型 函数名(参数表) = 0;
        注意：
            1. 纯虚函数不能被实例化，只能被派生类继承
            2. 纯虚函数不能被调用，只能被派生类实现
            3. 纯虚函数不能被重载，只能被派生类实现
            4. 纯虚函数不能被覆盖，只能被派生类实现
            5. 纯虚函数不能被声明为 const
            6. 纯虚函数不能被模板化
            7. 纯虚函数不能被友元函数
    虚继承：
        虚继承是指派生类从多个基类继承，且基类中有虚函数，派生类可以选择继承虚基类，
        这样也可以实现多态，即派生类可以根据需要调用基类中的虚函数。

四、可见性和友元函数(友元类)：
    访问权限(可见性)：
        public：公有成员，可以在任何地方访问，默认访问权限；
        private：私有成员，只能在类内部访问，不能在类的外部访问；
        protected：受保护成员，只能在类内部和派生类中访问，不能在类的外部访问；
        可见性只是为了方便程序员管理代码，编译器并不关心可见性，编译器只关心函数调用。
    友元函数是类的成员函数，它可以访问类的所有成员。
    友元函数的声明格式：
        friend 返回类型 函数名(类名& obj, [参数表]);   // 类名也可以是指针；不过，一般来说引用更好
    友元函数的作用：
        友元函数可以访问类的公有成员、私有和保护成员
*/

// 构造函数与析构函数示例
class Person
{
public:
    char name[50];
    char gender[10];
    int age;
    float height, weight;

    Person() { // 构造函数
        name[0] = '\0';
        gender[0] = '\0';
        age = 0;  // 也许其他语言中整数和浮点数会自动初始化为 0 可以不写，但 C++ 要求必须写
        height = 0.0;
        weight = 0.0;
        std::cout << "Person()构造函数被调用" << std::endl;
    }

    ~Person() { // 析构函数
        std::cout << "~Person()析构函数被调用" << std::endl;
    }

    Person(char* n, char* g, int a, float h, float w) {  // 构造函数重载
        strcpy_s(name, n);
        strcpy_s(gender, g);
        age = a;
        height = h;
        weight = w;
        std::cout << "Person(char *n, char *g, int a, float h, float w)构造函数被调用"
            << std::endl;
    }

    void print_info() {
        std::cout << "姓名：" << name << std::endl;
        std::cout << "性别：" << gender << std::endl;
        std::cout << "年龄：" << age << std::endl;
        std::cout << "身高：" << height << std::endl;
        std::cout << "体重：" << weight << std::endl;
    }

private:
    // 私有构造函数，不能实例化对象
    Person(char* n) {
        strcpy_s(name, n);
        gender[0] = '\0';
        age = 0;
        height = 0.0;
        weight = 0.0;
        std::cout << "Person(char *n)构造函数被调用" << std::endl;
    }
};

static void test_destroy() {
    Person p3;
    p3.print_info();
    p3.~Person(); // 手动调用析构函数，输出：~Person()析构函数被调用

    char n4[] = "李四";
    char g4[] = "男";
    Person p4(n4, g4, 25, 160.5f, 50.0f);
    p4.print_info();
} // 退出作用域，p3 和 p4 自动调用析构函数，输出：~Person()析构函数被调用，前面调用了也会再次调用

// 继承、多态、虚函数与纯虚函数示例
class Base
{
public:
    virtual std::string GetName() { return "Base"; } // 虚函数声明，virtual 主要用于生成虚函数表
    // 重写 string 基类虚函数 GetName 返回字符串 "Base"
    // 在使用 virtual 关键字后，后占用一小部分内存用于存放虚函数表，
    // 调用时需要遍历虚函数表找到正确的函数地址消耗少量性能，所以嵌入式系统中能不用 virtual 关键字就要用。
    //virtual std::string GetName() const { return "Base"; } // 常函数声明，不能被虚函数重载
};

class Derived : public Base
{ // 继承父类后，子类也可以是父类对象
private:
    std::string d_name;
public:
    Derived(const std::string& name) // 构造函数，参数为派生类独有，这里使用引用避免拷贝构造
        : d_name(name) {
    } // 这里将 d_name 初始化为创建类时传入的 name
    std::string GetName() override { return d_name; } // override 是 C++11 关键字，用于指明重写
    // 重写基类虚函数 GetName，返回派生类独有名称，使用 overrider 关键字声明覆盖，更加清晰。
};

static void print_name(Base* b) { std::cout << b->GetName() << std::endl; }

class NameShow
{
public:
    virtual std::string GetClassName() = 0; // 虚函数声明，用于打印类名
};

class Base2 : public NameShow
{
    //virtual std::string GetName() = 0; // 纯虚函数声明，不能实例化对象
    std::string GetClassName() override // 使用纯虚函数模拟实现上面的 GetName()
    {
        return "Base2";
    } // 纯虚函数重写，返回类名
};

class Derived2 : public Base2
{
private:
    std::string d2_name;
public:
    Derived2(const std::string& name) : d2_name(name) {}
    //std::string GetName() override { return d2_name; } // 重写纯虚函数 GetName
    std::string GetClassName() override { return "Derived2"; } // 重写纯虚函数 GetClassName
};

class A : public NameShow
{ // 纯虚函数再次举例
public:
    std::string GetClassName() override { return "A"; } // 重写纯虚函数 GetClassName
};

void print_virtual_name(NameShow* obj) { std::cout << obj->GetClassName() << std::endl; }

// 可见性和友元函数示例
class BaseFriendClass
{
protected: // 受保护成员，派生类可以访问
    float x, y;
    friend void PrintCoordinates(const BaseFriendClass& bfc);
    // 友元函数都需要重写，所以这里不做具体实现。
public:
    BaseFriendClass() : x(0.0f), y(0.0f) {} // 默认构造函数
    BaseFriendClass(float x, float y) : x(x), y(y) {} // 构造函数重载
    void SetCoordinates(float dx, float dy) { x = dx; y = dy; } // 公有成员函数
};

class FriendClass : public BaseFriendClass
{
private: // 私有成员，只能在类内部访问
    float m_value = 0.0f;
    friend void PrintCoordinates(const FriendClass& fc); // 声明友元函数

public:
    FriendClass(float value) : m_value(value) {} // 构造函数
    void SetValue(float dx, float dy, float value) {
        x = dx; // ok 可以访问父类 protected 成员
        y = dy; // ok
        m_value = value;
    }
};

void PrintCoordinates(const BaseFriendClass& bfc) { // 友元函数定义，这个函数不属于任何类
    std::cout << "BaseFriendClass 坐标：" << bfc.x << " " << bfc.y << std::endl;
}

void PrintCoordinates(const FriendClass& fc) { // 重载的核心：[同类]同函数名不同参数
    std::cout << "FriendClass 坐标：" << fc.x << " " << fc.y << std::endl;
    std::cout << "FriendClass 的 m_value 值：" << fc.m_value << std::endl;
}

void test_base_function() {
    print_start("C++ 中简单了解类中的基本函数");
    Person p1;
    p1.print_info();
    char n2[] = "张三";
    char g2[] = "男";
    Person p2(n2, g2, 20, 170.5f, 60.0f);
    p2.print_info();
    //Person p3(n2); // error: 不能访问私有构造函数
    test_destroy();
    std::cout << "--------------------------------------" << std::endl;

    Base* b1 = new Base();
    print_name(b1); // 输出 "Base"
    Derived* d1 = new Derived("Derived");
    print_name(d1); // 输出 "Derived"
    Base2* b2 = new Base2();
    print_virtual_name(b2); // 输出 "Base2"
    Derived2* d2 = new Derived2("Derived2");
    print_virtual_name(d2); // 输出 "Derived2"
    A* a1 = new A();
    print_virtual_name(a1); // 输出 "A"
    //print_virtual_name(new A()); // 也可以输出 "A" 会造成内存泄漏
    delete b1, d1, b2, d2, a1; // 释放内存
    std::cout << "--------------------------------------" << std::endl;

    BaseFriendClass bfc1;
    //bfc1.x = 1.0f; bfc1.y = 2.0f; // error: 不能访问受保护(protected)成员
    bfc1.SetCoordinates(1.5f, 2.5f);
    PrintCoordinates(bfc1); // 输出 "坐标：1.5 2.5"
    FriendClass fc1(1.5f);
    fc1.SetValue(2.5, 3.5, 1.5f); // ok
    PrintCoordinates(fc1); // 输出 "坐标：2.5 3.5 \n 值：1.5"
    print_end("C++ 中简单了解类中的基本函数");
} // 退出作用域，p1 和 p2 自动调用析构函数，输出：~Person()析构函数被调用

